/**
 * Copyright (c) 2010, Casey Lai
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 * 
 * Neither the name of the Cheetah Group nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package org.cheetahworkflow.designer.utils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.cheetahworkflow.designer.model.Chart;
import org.cheetahworkflow.designer.model.Comment;
import org.cheetahworkflow.designer.model.ConditionArrow;
import org.cheetahworkflow.designer.model.DirectionArrow;
import org.cheetahworkflow.designer.model.EndNode;
import org.cheetahworkflow.designer.model.LogicNode;
import org.cheetahworkflow.designer.model.ModuleNode;
import org.cheetahworkflow.designer.model.SequenceNode;
import org.cheetahworkflow.designer.model.StartNode;
import org.cheetahworkflow.designer.model.SwitchNode;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.jface.dialogs.MessageDialog;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXParseException;


public class XMLDeserializer {
	
	// creating an xml in eclipse, minimal bytes
	private static final int MINIMAL_XML_SIZE = 40;
	
	private static final String START_NODE_NAME = "START";
	
	// keep the node name and node in this map in order to lookup easily
	private Map<String, LogicNode> nodeMap = new HashMap<String, LogicNode>();
	
	private Node root = null;
	
	private XPath xpath = XPathFactory.newInstance().newXPath();
	
	public XMLDeserializer(InputStream in) throws Exception {
		// ignore empty file
		if (in.available() > MINIMAL_XML_SIZE) {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			try {
				Document doc = builder.parse(in);
				root = doc.getDocumentElement();
			} catch (SAXParseException e) {
				MessageDialog.openError(null, "Open File Error", "Cannot parse the file: " + e.getMessage());
			}
		}
	}
	
	/**
	 * Make sure operation of deserializing is successful. If the method return false,
	 * to invoke any other method is illegal, exception will be thrown out.
	 * @return if the operation of deserializing is successful 
	 */
	public boolean isValid() {
		return root != null;
	}
	
	public void addStartNode(Chart chart) {
		String attr = getAttributeValue("/workflow/start/@location");
		if (!attr.isEmpty()) {
			StartNode startNode = new StartNode(parseLocation(attr));
			nodeMap.put(START_NODE_NAME, startNode);
			chart.addNode(startNode);
		}
	}
	
	public void addLogicNodes(Chart chart) {
		List<LogicNode> nodes = new ArrayList<LogicNode>();
		NodeList list = getNodeList("/workflow/node");
		
		for(int i = 0, len = list.getLength(); i < len; i++) {
			Node node = list.item(i);
			
			String name = getAttributeValue("./@name", node);
			String caption = getNodeContent("./caption", node);
			Point location = parseLocation(getAttributeValue("./@location", node));
			Dimension size = parseSize(getAttributeValue("./@size", node));
			String description = getNodeContent("./description", node);
			String type = getAttributeValue("./@type", node);
			
			if (XMLSerializer.TYPE_SEQUENCE.equals(type)) {
				String className = getAttributeValue("./class/@name", node);
				String parameter = getNodeContent("./class/parameter", node);
				LogicNode logicNode = new SequenceNode(caption, location, size, description, className, parameter);
				nodeMap.put(name, logicNode);
				nodes.add(logicNode);
			} else if (XMLSerializer.TYPE_SWITCH.equals(type)) {
				LogicNode logicNode = new SwitchNode(caption, location, size, description);
				nodeMap.put(name, logicNode);
				nodes.add(logicNode);
			} else if (XMLSerializer.TYPE_MODULE.equals(type)) {
				String ref = getNodeContent("./ref", node);
				String classpath = getNodeContent("./ref-classpath", node);
				LogicNode logicNode = new ModuleNode(caption, location, size, description, ref, classpath);
				nodeMap.put(name, logicNode);
				nodes.add(logicNode);
			} else {
				MessageDialog.openError(null, "Parse Error", "Cannot identify the type of node: " + type);
			}
		}
		
		chart.addNodes(nodes);
	}
	
	public void addEndNodes(Chart chart) {
		List<EndNode> nodes = new ArrayList<EndNode>();
		NodeList list = getNodeList("/workflow/end");
		
		for(int i = 0, len = list.getLength(); i < len; i++) {
			Node node = list.item(i);
			String name = getAttributeValue("./@name", node);
			Point location = parseLocation(getAttributeValue("./@location", node));
			EndNode endNode = new EndNode(location);
			nodeMap.put(name, endNode);
			nodes.add(endNode);
		}
		
		chart.addNodes(nodes);
	}
	
	public void addArrows(Chart chart) {
		// add start arrow
		String startNodeArrowTarget = getNodeContent("/workflow/start");
		if (!startNodeArrowTarget.isEmpty()) {
			StartNode startNode = (StartNode) nodeMap.get(START_NODE_NAME);
			LogicNode targetNode = nodeMap.get(startNodeArrowTarget);
			new DirectionArrow(startNode, targetNode);
		}
		
		// add logic node arrow
		NodeList logicNodes = getNodeList("/workflow/node");
		for(int i = 0, len = logicNodes.getLength(); i < len; i++) {
			Node node = logicNodes.item(i);
			String name = getAttributeValue("./@name", node);
			LogicNode currentNode = nodeMap.get(name);
			String type = getAttributeValue("./@type", node);
			if (XMLSerializer.TYPE_SEQUENCE.equals(type) || XMLSerializer.TYPE_MODULE.equals(type)) {
				String nextNodeName = getNodeContent("./next-node", node);
				if (!nextNodeName.isEmpty()) {
					LogicNode nextNode = nodeMap.get(nextNodeName);
					new DirectionArrow(currentNode, nextNode);
				}
			} else if (XMLSerializer.TYPE_SWITCH.equals(type)) {
				NodeList switches = getNodeList("./switch", node);
				for(int j = 0, leng = switches.getLength(); j < leng; j++) {
					Node switchNode = switches.item(j);
					Node conditionNode = getNode("./condition", switchNode);
					if (conditionNode != null) {
						String className = getNodeContent("./@class", conditionNode);
						String parameter = getNodeContent("./parameter", conditionNode);
						String nextNodeName = getNodeContent("./next-node", switchNode);
						LogicNode nextNode = nodeMap.get(nextNodeName);
						new ConditionArrow(currentNode, nextNode, className, parameter, false);
					} else {
						String defaultNodeName = getNodeContent("./default-node", switchNode);
						LogicNode nextNode = nodeMap.get(defaultNodeName);
						new ConditionArrow(currentNode, nextNode, "", "", true);
					}
				}
			}
		}
	}
	
	public void addComments(Chart chart) {
		List<Comment> list = new ArrayList<Comment>();
		NodeList comments = getNodeList("/workflow/comment");
		
		for(int i = 0, len = comments.getLength(); i < len; i++) {
			Node node = comments.item(i);
			Point location = parseLocation(getAttributeValue("./@location", node));
			Dimension size = parseSize(getAttributeValue("./@size", node));
			String caption = getNodeContent(".", node);
			Comment comment = new Comment(caption, location, size);
			list.add(comment);
		}
		
		chart.addNodes(list);
	}
	
	private Point parseLocation(String attr) {
		String[] xy = attr.split("\\,");
		Point location = new Point(Integer.parseInt(xy[0]), Integer.parseInt(xy[1]));
		return location;
	}
	
	private Dimension parseSize(String attr) {
		String[] wh = attr.split("\\,");
		Dimension size = new Dimension(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
		return size;
	}
	
	private Node getNode(String exp) {
		try {
			return (Node) xpath.evaluate(exp, root, XPathConstants.NODE);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
	
	private Node getNode(String exp, Node node) {
		try {
			return (Node) xpath.evaluate(exp, node, XPathConstants.NODE);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
	
	private String getNodeContent(Node node) {
		if (node != null) {
			return node.getTextContent();
		} else {
			return "";
		}
	}
	
	private String getNodeContent(String exp) {
		return getNodeContent(getNode(exp));
	}
	
	private String getNodeContent(String exp, Node node) {
		return getNodeContent(getNode(exp, node));
	}
	
	private NodeList getNodeList(String exp) {
		try {
			return (NodeList) xpath.evaluate(exp, root, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
	
	private NodeList getNodeList(String exp, Node node) {
		try {
			return (NodeList) xpath.evaluate(exp, node, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
	
	private String getAttributeValue(String exp) {
		try {
			return (String) xpath.evaluate(exp, root, XPathConstants.STRING);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
	
	private String getAttributeValue(String exp, Node node) {
		try {
			return (String) xpath.evaluate(exp, node, XPathConstants.STRING);
		} catch (XPathExpressionException e) {
			throw new IllegalArgumentException("Occur error when parsing xpath: " + exp);
		}
	}
}
